﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Diagnostics.Contracts;

using SimpleIR;
using PInfoModel;

namespace PIPostAnalysis.StructureSpecs
{
	/// <summary>
	/// This class represents a checked escape structural specification.
	/// </summary>
	public sealed class StructureSpec
	{
		public readonly Dictionary<IRType, TypeStructureSpec> Specs;

		public StructureSpec()
		{
			this.Specs = new Dictionary<IRType, TypeStructureSpec>();
		}

		public string EmitSpecsString(HashSet<IRType> immutabletypes, HashSet<IRType> singletontypes)
		{
			StringBuilder sb = new StringBuilder();

			foreach(TypeStructureSpec tss in this.Specs.Values)
			{
				sb.Append(tss.EmitFullString(immutabletypes, singletontypes));
				sb.Append("\n");
			}

			return sb.ToString();
		}

		/// <summary>
		/// Extend the specs with the infor from the given model.
		/// </summary>
		public void ProcessSinglePIModel(PIModel pim)
		{
			Dictionary<PIMNode, NodeStructureSpec> structinfo = StructSpecExtractor.ComputeStructSpec(pim);

			foreach(PIMNode nn in pim.Nodes)
			{
				if(nn.HomeLocFor != null || nn.LocationType.TypeDescription.IsBuiltinType)
					continue;

				if(!this.Specs.ContainsKey(nn.LocationType))
					this.Specs.Add(nn.LocationType, new TypeStructureSpec(nn.LocationType));

				TypeStructureSpec tss = this.Specs[nn.LocationType];

				foreach(PIMNode cnode in structinfo[nn].Captured)
					tss.UpdateCaptured(cnode.LocationType, true);

				foreach(IRBasicAccessPath bap in nn.LocationType.TypeDescription.AllTerminalAccessPaths)
				{
					if(bap.PathLast.FieldType is IRPtrType)
					{
						if(nn.OutEdges.Count((PIMEdge oe) => oe.OffsetInSource == bap) == 0 || nn.OutEdges.Any((PIMEdge oe) => oe.OffsetInSource == bap && !oe.IsNonNull))
							tss.MarkFieldMayNull(bap);
					}
				}

				foreach(PIMEdge oe in nn.OutEdges)
				{
					IEnumerable<PIMNode> exp = structinfo[nn].Exposed.Where((Tuple<IRBasicAccessPath, PIMNode> tp) => tp.Item1 == oe.OffsetInSource).Select((Tuple<IRBasicAccessPath, PIMNode> tp) => tp.Item2);
					HashSet<PIMNode> expset = new HashSet<PIMNode>(exp);
					HashSet<IRType> exptys = new HashSet<IRType>(expset.Select((PIMNode exn) => exn.LocationType));

					IEnumerable<PIMNode> prp = structinfo[nn].Propagated.Where((Tuple<IRBasicAccessPath, PIMNode> tp) => tp.Item1 == oe.OffsetInSource).Select((Tuple<IRBasicAccessPath, PIMNode> tp) => tp.Item2);
					HashSet<PIMNode> prpset = new HashSet<PIMNode>(prp);
					HashSet<IRType> prptys = new HashSet<IRType>(prpset.Select((PIMNode exn) => exn.LocationType));

					foreach(PIMNode expn in expset)
					{
						if(!structinfo[nn].Captured.Contains(expn))
							tss.UpdateCaptured(expn.LocationType, false);
					}

					foreach(PIMNode propn in prpset)
					{
						if(!structinfo[nn].Captured.Contains(propn))
							tss.UpdateCaptured(propn.LocationType, false);
					}

					foreach(IRType expty in exptys)
						tss.AddExposed(expty, oe.OffsetInSource);

					foreach(IRType prpty in prptys)
						tss.AddPropagated(prpty, oe.OffsetInSource);

					bool isptrtocontainer = (oe.OffsetInSource.PathLast.FieldType is IRPtrType) && (((IRPtrType)oe.OffsetInSource.PathLast.FieldType).PtrTargetType is IRContainerType);
					if(isptrtocontainer && ((IRContainerType)((IRPtrType)oe.OffsetInSource.PathLast.FieldType).PtrTargetType).ContainerContentsType is IRPtrType)
						this.ProcessSingleContainerTarget(nn, oe.OffsetInSource, pim.Nodes[oe.TargetAddress]);
				}
			}
		}

		private void ProcessSingleContainerTarget(PIMNode nn, IRBasicAccessPath cbap, PIMNode cnode)
		{
			TypeStructureSpec tss = this.Specs[nn.LocationType];

			if((cnode.OutEdges.Count == 0 && !cnode.ContainerMustBeEmpty) || (cnode.OutEdges.Count != 0 && cnode.OutEdges.Any((PIMEdge ee) => !ee.IsNonNull)))
				tss.MarkContainerContentsMayNull(cbap);

			if(cnode.OutEdges.Any((PIMEdge ee) => !ee.IsInjective))
				tss.MarkContainerMayAlias(cbap);
		}
	}

	/// <summary>
	/// This class contains information on specifications for a single type.
	/// </summary>
	public sealed class TypeStructureSpec
	{
		/// <summary>
		/// The type that this spec is on.
		/// </summary>
		public readonly IRType OnType;

		/// <summary>
		/// The set of exposed locations that are captured in this type.
		/// </summary>
		public readonly Dictionary<IRType, bool> CapturedInfo;

		/// <summary>
		/// The set of locations that are exposed for the first time via this type.
		/// </summary>
		public readonly HashSet<Tuple<IRBasicAccessPath, IRType>> MayExposed;

		/// <summary>
		/// The set of locations that are exposed by other types and the exposure is propagated by this type.
		/// </summary>
		public readonly HashSet<Tuple<IRBasicAccessPath, IRType>> MayPropagated;

		/// <summary>
		/// A set of all paths in this that are known to be non null.
		/// </summary>
		public readonly HashSet<IRBasicAccessPath> MustNonNullFieldSet;

		/// <summary>
		/// A set of all paths in this that are known to point to containers that do not contian aliases.
		/// </summary>
		public readonly HashSet<IRBasicAccessPath> MustContainerPathToAliasFreeSet;

		/// <summary>
		/// A set of all paths in this that are known to containers that are known to not contain null.
		/// </summary>
		public readonly HashSet<IRBasicAccessPath> MustContainerPathToNullFreeSet;

		public TypeStructureSpec(IRType fortype)
		{
			this.OnType = fortype;

			this.CapturedInfo = new Dictionary<IRType, bool>();

			this.MayExposed = new HashSet<Tuple<IRBasicAccessPath, IRType>>();
			this.MayPropagated = new HashSet<Tuple<IRBasicAccessPath, IRType>>();

			this.MustNonNullFieldSet = new HashSet<IRBasicAccessPath>();

			this.MustContainerPathToAliasFreeSet = new HashSet<IRBasicAccessPath>();
			this.MustContainerPathToNullFreeSet = new HashSet<IRBasicAccessPath>();

			foreach(IRBasicAccessPath bap in fortype.TypeDescription.AllTerminalAccessPaths)
			{
				if(bap.PathLast.FieldType is IRPtrType)
					this.MustNonNullFieldSet.Add(bap);

				if((bap.PathLast.FieldType is IRPtrType) && (((IRPtrType)bap.PathLast.FieldType).PtrTargetType is IRContainerType))
				{
					this.MustContainerPathToAliasFreeSet.Add(bap);

					if(((IRContainerType)((IRPtrType)bap.PathLast.FieldType).PtrTargetType).ContainerContentsType is IRPtrType)
						this.MustContainerPathToNullFreeSet.Add(bap);
				}
			}
		}

		public void AddExposed(IRType tt, IRBasicAccessPath accp)
		{
			this.MayExposed.Add(new Tuple<IRBasicAccessPath, IRType>(accp, tt));
		}

		public void AddPropagated(IRType tt, IRBasicAccessPath onpath)
		{
			this.MayPropagated.Add(new Tuple<IRBasicAccessPath,IRType>(onpath, tt));
		}

		public void UpdateCaptured(IRType capt, bool okcap)
		{
			if(!this.CapturedInfo.ContainsKey(capt))
				this.CapturedInfo.Add(capt, okcap);
			else
				this.CapturedInfo[capt] = this.CapturedInfo[capt] & okcap;
		}

		public void MarkFieldMayNull(IRBasicAccessPath bap)
		{
			this.MustNonNullFieldSet.Remove(bap);
		}

		public void MarkContainerMayAlias(IRBasicAccessPath cpt)
		{
			this.MustContainerPathToAliasFreeSet.Remove(cpt);
		}

		public void MarkContainerContentsMayNull(IRBasicAccessPath cpt)
		{
			this.MustContainerPathToNullFreeSet.Remove(cpt);
		}

		public void MergeTypeSpecInto(TypeStructureSpec other)
		{
			foreach(KeyValuePair<IRType, bool> okcap in other.CapturedInfo)
			{
				if(this.CapturedInfo.ContainsKey(okcap.Key))
					this.CapturedInfo[okcap.Key] = this.CapturedInfo[okcap.Key] & okcap.Value;
				else
					this.CapturedInfo.Add(okcap.Key, okcap.Value);
			}

			this.MayExposed.UnionWith(other.MayExposed);
			this.MayPropagated.UnionWith(other.MayPropagated);

			this.MustNonNullFieldSet.IntersectWith(other.MustNonNullFieldSet);

			this.MustContainerPathToAliasFreeSet.IntersectWith(other.MustContainerPathToAliasFreeSet);
			this.MustContainerPathToNullFreeSet.IntersectWith(other.MustContainerPathToNullFreeSet);
		}

		private static bool SuppressSimpleTypesCheck(IRType tt, HashSet<IRType> immutabletypes, HashSet<IRType> singletontypes)
		{
			if(singletontypes.Contains(tt))
				return true;

			if(immutabletypes.Contains(tt) || tt.TypeName.Equals("System.String"))
				return true;

			return false;
		}

		public string EmitFullString(HashSet<IRType> immutabletypes, HashSet<IRType> singletontypes)
		{
			StringBuilder sb = new StringBuilder("TypeSpec for " + this.OnType.ToString() + "\n");

			if(immutabletypes.Contains(this.OnType))
				sb.Append("\tImmutable\n");

			if(singletontypes.Contains(this.OnType))
				sb.Append("\tSingleton\n");

			int capcount = this.CapturedInfo.Count((KeyValuePair<IRType, bool> tbv) => !tbv.Key.TypeName.Equals("System.String") && tbv.Value);
			if(capcount != 0)
			{
				List<IRType> cpl = new List<IRType>();
				foreach(KeyValuePair<IRType, bool> cv in this.CapturedInfo)
				{
					if(cv.Value)
						cpl.Add(cv.Key);
				}

				cpl.Sort((IRType t1, IRType t2) => t1.TypeName.CompareTo(t2.TypeName));
				cpl.RemoveAll((IRType tt) => tt.TypeName.Equals("System.String"));
				
				sb.Append("\tMustCapture={");
				for(int i = 0; i < cpl.Count; ++i)
				{
					if(i != 0)
						sb.Append(", ");
					sb.Append(cpl[i].TypeName);
				}
				sb.Append("}\n");
			}

			foreach(IRBasicAccessPath bap in this.OnType.TypeDescription.AllTerminalAccessPaths)
			{
				if(bap.PathLast.FieldName.Equals("rtti"))
					continue;

				sb.Append("\t");

				if(bap.PathLast.FieldType is IRPtrType)
				{
					if(!MustNonNullFieldSet.Contains(bap))
						sb.Append("?");
				}

				if((bap.PathLast.FieldType is IRPtrType) && (((IRPtrType)bap.PathLast.FieldType).PtrTargetType is IRContainerType))
					sb.Append(EmitContainerType(bap, immutabletypes, singletontypes));
				else
					sb.Append(bap.PathLast.FieldType.TypeName.Replace("<", "#<"));

				sb.Append(" " + bap.FieldOnlyDisplayString());

				IEnumerable<IRType> extenum = this.MayExposed.Where((Tuple<IRBasicAccessPath, IRType> ttp) => ttp.Item1 == bap).Select((Tuple<IRBasicAccessPath, IRType> ttp) => ttp.Item2);
				HashSet<IRType> exps = new HashSet<IRType>(extenum);
				exps.RemoveWhere((IRType tt) => tt.TypeName.Equals("System.String"));

				IEnumerable<IRType> propenum = this.MayPropagated.Where((Tuple<IRBasicAccessPath, IRType> ttp) => ttp.Item1 == bap).Select((Tuple<IRBasicAccessPath, IRType> ttp) => ttp.Item2);
				HashSet<IRType> props = new HashSet<IRType>(propenum);
				props.RemoveWhere((IRType tt) => tt.TypeName.Equals("System.String"));
				
				if(exps.Count != 0)
				{
					List<IRType> expl = new List<IRType>(exps);
					expl.Sort((IRType t1, IRType t2) => t1.TypeName.CompareTo(t2.TypeName));

					sb.Append(":Share[");
					for(int i = 0; i < expl.Count; ++i)
					{
						if(i != 0)
							sb.Append(", ");
						sb.Append(expl[i].TypeName);
					}
					sb.Append("]");
				}

				if(props.Count != 0)
				{
					List<IRType> propl = new List<IRType>(props);
					propl.Sort((IRType t1, IRType t2) => t1.TypeName.CompareTo(t2.TypeName));

					sb.Append(":Propagate[");
					for(int i = 0; i < propl.Count; ++i)
					{
						if(i != 0)
							sb.Append(", ");
						sb.Append(propl[i].TypeName);
					}
					sb.Append("]");
				}

				sb.Append("\n");
			}

			sb.Append("\n");
			return sb.ToString();
		}

		private string EmitContainerType(IRBasicAccessPath bapcp, HashSet<IRType> immutabletypes, HashSet<IRType> singletontypes)
		{
			IRContainerType conty = (IRContainerType)((IRPtrType)bapcp.PathLast.FieldType).PtrTargetType;
			if(conty is IRDictionaryType)
			{
				IRKeyValuePairType kvpty = (IRKeyValuePairType)((IRDictionaryType)conty).ContainerContentsType;

				string nulls = this.MustContainerPathToNullFreeSet.Contains(bapcp) ? "" : "?";
				string acp = this.MustContainerPathToAliasFreeSet.Contains(bapcp) ? "" : "!";

				string kss = kvpty.KeyField.FieldType.TypeName.Replace("<", "#<");
				string nullsk = kvpty.KeyField.FieldType is IRPtrType ? nulls : "";

				string vss = kvpty.ValueField.FieldType.TypeName.Replace("<", "#<");
				string nullsv = kvpty.ValueField.FieldType is IRPtrType ? nulls : "";
				string acpv = kvpty.ValueField.FieldType is IRPtrType ? acp : "";

				return "System.Collections.Generic.Dictionary<" + nullsk + kss + ", " + nullsv + acpv + vss + ">";
			}
			else
			{
				bool pcts = conty.ContainerContentsType is IRPtrType;
				string nulls = "";
				string acp = ""; 

				if(pcts)
				{
					nulls = this.MustContainerPathToNullFreeSet.Contains(bapcp) ? "" : "?";
					acp = (this.MustContainerPathToAliasFreeSet.Contains(bapcp) || conty.IsSetBehaviorContainer) ? "" : "!";
				}

				if(conty.IsArrayContainer)
					return conty.ContainerContentsType.TypeName + "[" + nulls + acp + "]";
				else
				{
					int ipos = conty.TypeName.IndexOf('<');
					string istr = conty.TypeName.Substring(0, ipos + 1);
					string cstr = conty.ContainerContentsType.TypeName.Replace("<", "#<");

					return istr + nulls + acp + cstr + ">";
				}
			}
		}
	}
}
